package com.example.sources;


import com.example.sources.handler.AbstractHandler;
import com.example.sources.handler.InsertHandler;
import com.example.sources.handler.SelectHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author liyuncong
 * @version 1.0
 * @file MapperProxyFactory
 * @brief MapperProxyFactory
 * @details MapperProxyFactory
 * @date 2022-11-13
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2022-11-13               liyuncong          Created
 */
public class MapperProxyFactory {

    private static final Logger logger = LoggerFactory.getLogger(MapperProxyFactory.class);
    private static final List<AbstractHandler> handlers = new ArrayList<>();

    static {
        try {
            Class.forName("org.postgresql.Driver");
            handlers.add(new SelectHandler());
            handlers.add(new InsertHandler());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static <T> T getMapper(Class<T> clazz) {
        final Object instance = Proxy.newProxyInstance(
            ClassLoader.getSystemClassLoader(),
            new Class[] {clazz},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    final Connection connection = getConnection();

                    for (AbstractHandler handler : handlers) {
                        final String sql = handler.parseSql(method);
                        if (null != sql) {
                            final Object result = handler.process(method, args, sql, connection);
                            return result;
                        }
                    }
                    logger.error("sql error");
                    return null;
//                    final String oldSql = getAnnotationValue(method);
//                    if (null == oldSql) {
//                        logger.error("sql is null");
//                        throw new RuntimeException();
//                    }
//                    logger.info("current sql {}", oldSql);
//
//                    final Map<String, Object> paramValueMappingMap =
//                        paramValueMappingMap(method.getParameters(), args);
//
//                    final Map<String, Object> sqlMap = parseSql(oldSql);
//
//                    final String newSql = (String) sqlMap.get("sql");
//                    final List<String> paramsIndex = (List<String>) sqlMap.get("params");
//
//                    final PreparedStatement statement = connection.prepareStatement(newSql);
//                    processSql(statement, paramValueMappingMap, paramsIndex);
//
//                    final ResultSet resultSet = statement.executeQuery();
//                    List<Userinfo> list = new ArrayList<>();
//                    while (resultSet.next()) {
//                        final int id = resultSet.getInt("id");
//                        final String username = resultSet.getString("username");
//                        final int age = resultSet.getInt("age");
//                        final int gender = resultSet.getInt("gender");
//                        final Userinfo userinfo = new Userinfo(id, username, age, gender);
//                        list.add(userinfo);
//                    }
//
//                    resultSet.close();
//                    statement.close();
//                    connection.close();
//                    return list;
                }
            });
        return (T) instance;
    }

    private static Connection getConnection() {
        String url = "jdbc:postgresql://localhost/java-demo";
        String username = "postgres";
        String password = "123456";
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    private static String getAnnotationValue(Method method) {
        final Select select = method.getAnnotation(Select.class);
        if (null != select) {
            return select.value();
        }
        final Insert insert = method.getAnnotation(Insert.class);
        if (null != insert) {
            return insert.value();
        }
        return null;
    }

    private static Map<String, Object> paramValueMappingMap(
        Parameter[] parameters, Object[] values
    ) {
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < parameters.length; i++) {
            final Parameter parameter = parameters[i];
            final String name = parameter.getAnnotation(Param.class).value();
            map.put(name, values[i]);
        }
        return map;
    }

    private static Map<String, Object> parseSql(String sql) {
        final String[] split = sql.split("#");
        List<String> paramsIndex = new ArrayList<>();
        for (int i = 1; i < split.length; i++) {
            final String s = split[i];
            final String param = s.split("}")[0].substring(1);
            paramsIndex.add(i - 1, param);
        }
        Pattern pattern = Pattern.compile("#.*?}");
        Matcher matcher = pattern.matcher(sql);
        final String newSql = matcher.replaceAll("?");
        Map<String, Object> result = new HashMap<>();
        result.put("sql", newSql);
        result.put("params", paramsIndex);
        return result;
    }

    private static void processSql(
        PreparedStatement statement,
        Map<String, Object> paramValueMappingMap,
        List<String> paramsIndex
    ) {
        for (int i = 0; i < paramsIndex.size(); i++) {
            try {
                statement.setObject(i + 1, paramValueMappingMap.get(paramsIndex.get(i)));
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

//    public static void main(String[] args) {
//        String sql = "select * from userinfo where username = #{username} and age = #{age}";
//        final String[] split = sql.split("#");
//        for (String s : split) {
//            System.out.println(s);
//        }
//
//        for (int i = 1; i < split.length; i++) {
//            final String s = split[i];
//            final String split1 = s.split("}")[0];
//            final String substring = split1.substring(1);
//            System.out.println(substring);
//        }
//
//        final Map<String, Object> map = parseSql(sql);
//        System.out.println(map.get("sql"));
//        final List<String> params = (List<String>) map.get("params");
//        for (int i = 0; i < params.size(); i++) {
//            System.out.println("index = " + i + " name = " + params.get(i));
//        }

}
